﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Urb.Weather.Core.Manager;
using Urb.Weather.Core.Engine;
using System.Threading;
using Urb.Weather.Core.Configuration;
using Urb.Weather.Core.Container;
using Urb.Weather.Core.Provider;
using Urb.Weather.Core;
using Urb.Weather.Core.Util;

namespace Urb.Weather.Manager
{
    public class Manager : IManager
    {
        private IContainer container;
        private IEngine engine;
        private IConfigurationManager configurationManager;

        private Configuration configuration;
        private object configurationLock = new object();
        
        private object weatherDataLock = new object();
        private WeatherDataHistories weatherDataHistories = new WeatherDataHistories();

        private AtmosphereConditions atmosphereCurrentConditions = new AtmosphereConditions();
        private Forecast todayForecast = null;
        private List<Forecast> nextDaysForecast = new List<Forecast>();

        private WeatherDataProviderSourceNames providerSourceNames = new WeatherDataProviderSourceNames();

        public Manager(
            IContainer container,
            IEngine engine,
            IConfigurationManager configurationManager
            )
        {
            if (container == null)
                throw new ArgumentNullException("container");

            if (engine == null)
                throw new ArgumentNullException("engine");
            
            if (configurationManager == null)
                throw new ArgumentNullException("configurationManager");

            this.container = container;
            this.engine = engine;
            this.configurationManager = configurationManager;

            ThreadPool.QueueUserWorkItem((s) => StartEngine());
        }

        public void BeginLoadConfiguration()
        {
            this.engine.Queue(
                new WorkItem()
                {
                    Action =
                    () =>
                    {
                        lock (configurationLock)
                        {
                            configuration = configurationManager.Load();
                            BeginApplyConfiguration();
                        }
                    }
                }
                );
        }

        public event Action<AverageTemperatureConditions> TemperatureCurrentConditionsChanged;
        public event Action<AveragePressureConditions> PressureCurrentConditionsChanged;
        public event Action<AtmosphereConditions> AtmosphereCurrentConditionsChanged;
        public event Action<TemperatureConditions, TemperatureConditions> TemperatureExtremesChanged;
        public event Action<PressureConditions, PressureConditions> PressureExtremesChanged;
        public event Action<Forecast> TodayForecastChanged;
        public event Action<List<Forecast>> NextDaysForecastChanged;

        public event Action ConfigurationApplied;

        private void BeginApplyConfiguration()
        {
            this.engine.Queue(
                new WorkItem()
                {
                    Action =
                    () =>
                    {
                        lock (configurationLock)
                        {
                            ApplyConfiguration();
                        }
                    }
                }
                );
        }

        private void ApplyConfiguration()
        {
            // TODO: cleanup previous configuration

            this.providerSourceNames.Clear();
            uint providerCount = 0;

            foreach (var providerConfig in configuration.WeatherDataProviderConfigurations)
            {
                var provider = container.Resolve<IWeatherDataProvider>(providerConfig.ProviderType);
                var providerInstanceToken = new WeatherDataProviderInstanceRegistrationToken(providerCount);

                provider.InitializeInstance(providerInstanceToken, engine, providerConfig, DataRefreshed);

                providerSourceNames[providerInstanceToken] = provider.SourceName;

                providerCount++;
            }

            if (ConfigurationApplied != null)
                ConfigurationApplied();
        }

        public void Cleanup()
        {
            this.engine.RequestStop();
            this.engine.Stop();
        }

        private void StartEngine()
        {
            this.engine.Start();
        }

        private void DataRefreshed(IWeatherDataProviderInstanceRegistrationToken providerInstanceRegistrationToken, WeatherData weatherData)
        {
            lock (weatherDataLock)
            {
                if (weatherData == null)
                    return;

                if (!weatherData.MeasurementTime.HasValue)
                    return;

                var measurementTime = weatherData.MeasurementTime.Value;
                
                var history = weatherDataHistories.GetOrCreate(providerInstanceRegistrationToken, providerSourceNames);

                if (weatherData.TemperatureUnit.HasValue && weatherData.Temperature.HasValue)
                {
                    var updateResult = history.Temperature.Update(
                        measurementTime,
                        weatherData.Temperature.Value,
                        weatherData.TemperatureUnit.Value
                        );

                    if (updateResult.MostRecentChanged)
                    {
                        if (this.TemperatureCurrentConditionsChanged != null)
                            this.TemperatureCurrentConditionsChanged(weatherDataHistories.AverageMostRecentTemperatureConditions.Clone());
                    }
                    if (updateResult.ExtremesChanged)
                    {
                        if (this.TemperatureExtremesChanged != null)
                            this.TemperatureExtremesChanged(history.Temperature.Min.Clone(), history.Temperature.Max.Clone());
                    }
                }
                
                if (weatherData.PressureUnit.HasValue && weatherData.Pressure.HasValue)
                {
                    var updateResult = history.Pressure.Update(
                        measurementTime,
                        weatherData.Pressure.Value,
                        weatherData.PressureUnit.Value
                        );

                    if (updateResult.MostRecentChanged)
                    {
                        if (this.PressureCurrentConditionsChanged != null)
                            this.PressureCurrentConditionsChanged(weatherDataHistories.AverageMostRecentPressureConditions.Clone());
                    }
                    if (updateResult.ExtremesChanged)
                    {
                        if (this.PressureExtremesChanged != null)
                            this.PressureExtremesChanged(history.Pressure.Min.Clone(), history.Pressure.Max.Clone());
                    }
                }
            }


            lock (weatherDataLock)
            {
                if (weatherData == null)
                    return;

                if (!weatherData.MeasurementTime.HasValue)
                    return;

                if (this.atmosphereCurrentConditions == null && weatherData.AtmosphereConditions != null)
                {
                    this.atmosphereCurrentConditions = weatherData.AtmosphereConditions.Clone();
                    this.AtmosphereCurrentConditionsChanged(this.atmosphereCurrentConditions.Clone());
                }
                else if (this.atmosphereCurrentConditions != null && weatherData.AtmosphereConditions != null)
                {
                    if (this.atmosphereCurrentConditions.Code != weatherData.AtmosphereConditions.Code ||
                        this.atmosphereCurrentConditions.Description != weatherData.AtmosphereConditions.Description)
                    {
                        this.atmosphereCurrentConditions = weatherData.AtmosphereConditions.Clone();

                        if (this.AtmosphereCurrentConditionsChanged != null)
                            this.AtmosphereCurrentConditionsChanged(this.atmosphereCurrentConditions.Clone());
                    }
                }

                if (weatherData.Forecasts != null)
                {
                    var todayForecastData = weatherData.Forecasts.FirstOrDefault(wd => wd.DayOffset.HasValue && wd.DayOffset.Value == 0);

                    if (todayForecastData == null ||
                        !todayForecastData.DayOffset.HasValue ||
                        !todayForecastData.TemperatureMin.HasValue ||
                        !todayForecastData.TemperatureMax.HasValue ||
                        !todayForecastData.TemperatureUnit.HasValue)
                    {
                        if (this.todayForecast != null)
                        {
                            this.todayForecast = null;

                            if (this.TodayForecastChanged != null)
                                this.TodayForecastChanged(null);
                        }
                    }
                    else
                    {
                        if (this.todayForecast == null)
                            this.todayForecast = new Forecast();

                        this.todayForecast.DayOffset = 0;
                        this.todayForecast.ReferenceDate = weatherData.MeasurementTime.Value;

                        if (
                            this.todayForecast.TemperatureMin != todayForecastData.TemperatureMin.Value ||
                            this.todayForecast.TemperatureMax != todayForecastData.TemperatureMax.Value ||
                            this.todayForecast.TemperatureUnit != todayForecastData.TemperatureUnit.Value)
                        {
                            this.todayForecast.TemperatureMin = todayForecastData.TemperatureMin.Value;
                            this.todayForecast.TemperatureMax = todayForecastData.TemperatureMax.Value;
                            this.todayForecast.TemperatureUnit = todayForecastData.TemperatureUnit.Value;

                            if (this.TodayForecastChanged != null)
                                this.TodayForecastChanged(this.todayForecast.Clone());
                        }
                    }

                    var nextDaysForecastDatas = weatherData.Forecasts.Where(wd => wd.DayOffset.HasValue && wd.DayOffset.Value > 0).OrderBy(wd => wd.DayOffset.Value);
                    bool nextDaysForecastChanged = false;

                    if (nextDaysForecastDatas.Count() != this.nextDaysForecast.Count)
                    {
                        this.nextDaysForecast.Clear();
                        nextDaysForecastChanged = true;
                    }

                    List<Forecast> tmpNextDaysForecast = new List<Forecast>();

                    foreach (var nextDayForecastData in nextDaysForecastDatas)
                    {
                        if (nextDayForecastData == null)
                            continue;

                        if (
                            !nextDayForecastData.DayOffset.HasValue ||
                            !nextDayForecastData.TemperatureMin.HasValue ||
                            !nextDayForecastData.TemperatureMax.HasValue ||
                            !nextDayForecastData.TemperatureUnit.HasValue
                            )
                            continue;

                        tmpNextDaysForecast.Add(
                            new Forecast()
                            {
                                DayOffset = nextDayForecastData.DayOffset.Value,
                                ReferenceDate = weatherData.MeasurementTime.Value,
                                TemperatureMin = nextDayForecastData.TemperatureMin.Value,
                                TemperatureMax = nextDayForecastData.TemperatureMax.Value,
                                TemperatureUnit = nextDayForecastData.TemperatureUnit.Value
                            }
                            );
                    }

                    if (tmpNextDaysForecast.Count != nextDaysForecast.Count)
                        nextDaysForecastChanged = true;

                    if (!nextDaysForecastChanged)
                    {
                        for (int i = 0; i < nextDaysForecast.Count; i++)
                        {
                            var forecast1 = tmpNextDaysForecast[i];
                            var forecast2 = nextDaysForecast[i];

                            if (
                                forecast1.DayOffset != forecast2.DayOffset ||
                                forecast1.ReferenceDate != forecast2.ReferenceDate ||
                                forecast1.TemperatureMin != forecast2.TemperatureMin ||
                                forecast1.TemperatureMax != forecast2.TemperatureMax ||
                                forecast1.TemperatureUnit != forecast2.TemperatureUnit
                                )
                            {
                                nextDaysForecastChanged = true;
                                break;
                            }
                        }
                    }

                    if (nextDaysForecastChanged)
                    {
                        this.nextDaysForecast = tmpNextDaysForecast;

                        this.NextDaysForecastChanged(
                            new List<Forecast>(
                                this.nextDaysForecast.Select(f => f.Clone())
                                )
                            );
                    }
                }
            }
        }
    }
}
